/**
 * 
 */
package gov.nih.nlm.lowlinc.core;

import gov.nih.nlm.lowlinc.accessionwrapper.LOINCID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCPartID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCTermID;
import gov.nih.nlm.lowlinc.bean.LOINCPart;
import gov.nih.nlm.lowlinc.bean.LOINCTerm;
import gov.nih.nlm.lowlinc.core.MultiaxialComposer.ValueComparator;
import gov.nih.nlm.lowlinc.csv.AxialGraph;
import gov.nih.nlm.lowlinc.csv.InferredAxialOWLParser;
import gov.nih.nlm.lowlinc.csv.LOINCAxialCSVParser;
import gov.nih.nlm.lowlinc.csv.LOINCPartLinksCSVParser;
import gov.nih.nlm.lowlinc.csv.LOINCPartsCSVParser;
import gov.nih.nlm.lowlinc.csv.LOINCTermsCSVParser;
import gov.nih.nlm.lowlinc.owlapi.LowlincException;
import gov.nih.nlm.lowlinc.owlapi.Ontologies;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;

import org.apache.log4j.Logger;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.ClassExpressionType;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAnnotationProperty;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;

/**
 * @author Tomasz Adamusiak
 * 
 */
public class LowlincValidator {
	private static final Logger log = Logger.getLogger(LowlincValidator.class);
	private static PrintWriter out;

	/**
	 * @param args
	 * @throws LowlincException
	 */
	public static void main(String[] args) throws LowlincException {
		// compareOntologies();
		// findCircularEquivalence();
		analyzeHierarchy();
	}

	public static void analyzeHierarchy() throws LowlincException {
		log.info("Analysing hierarchy");

		// setup parsers
		AxialGraph axialGraph = new LOINCAxialCSVParser().getGraph();
		Map<LOINCTermID, LOINCTerm> terms = new LOINCTermsCSVParser()
				.getTerms();
		Map<LOINCPartID, LOINCPart> parts = new LOINCPartsCSVParser()
				.getParts();

		// create labels
		Map<LOINCID, String> labels = new HashMap<>();
		for (LOINCPart part : parts.values()) {
			labels.put(part.getPartID(), part.getLabel());
		}
		for (LOINCTerm term : terms.values()) {
			labels.put(term.getTermID(), term.getLabel());
		}

		AxialGraph inferredAxialGraph = new InferredAxialOWLParser(labels)
				.getGraph();
		Set<LOINCTermID> sTermsNew = inferredAxialGraph.getTerms();

		// identify missclassified terms
		Set<LOINCTermID> sMissedTerms = new HashSet<>();
		Set<LOINCTermID> sInactiveTerms = new HashSet<>();
		for (LOINCTermID termID : axialGraph.getTerms()) {
			if (!sTermsNew.contains(termID)) {
				LOINCTerm term = terms.get(termID);
				if (term.isActive()) {
					log.error("MISCLASSIFIED AXIAL TERM " + labels.get(termID));
					sMissedTerms.add(termID);
				} else {
					log.error("INACTIVE TERM IN MULTIAXIAL "
							+ labels.get(termID) + term.getSTATUS());
					sInactiveTerms.add(termID);
				}

			}
		}
		log.info("TOTAL MISCLASSIFIED AXIAL TERMS " + sMissedTerms.size());
		log.info("TOTAL INACTIVE AXIAL TERMS " + sInactiveTerms.size());

		Set<LOINCTermID> termsMoved = new HashSet<>();
		// find moved terms
		for (LOINCTermID termID : axialGraph.getTerms()) {
			if (terms.get(termID).isActive()) {
				Set<String> originalParentsNames = new HashSet<>();
				Set<String> inferredParentsNames = new HashSet<>();
				for (LOINCID loincID : axialGraph.getParents(termID)) {
					originalParentsNames.add(labels.get(loincID));
				}
				for (LOINCID loincID : inferredAxialGraph.getAllParents(termID)) {
					inferredParentsNames.add(labels.get(loincID).substring(4));
				}

				if (!inferredParentsNames.containsAll(originalParentsNames)) {
					log.error(terms.get(termID));
					log.error("ORIGINAL " + originalParentsNames);
					log.error("INFERRED " + inferredParentsNames);
					termsMoved.add(termID);
				}
			}

		}

		log.info("Terms moved " + termsMoved.size());

		// find still unclassfied terms
		Map<LOINCTermID, LOINCTerm> sTermsMissing = new HashMap<>(terms);
		for (LOINCTermID termID : sTermsNew) {
			sTermsMissing.remove(termID);
		}

		log.info(sTermsMissing.size() + " terms not in hierarchy");

		// compositionOfUnclassified(parts, sTermsMissing);
	}

	// find composistion of unclassified terms
	private static void compositionOfUnclassified(
			Map<LOINCPartID, LOINCPart> parts,
			Map<LOINCTermID, LOINCTerm> sTermsMissing) throws LowlincException {
		Map<LOINCTermID, List<LOINCPartID>> partLinks = new LOINCPartLinksCSVParser()
				.getLinks();

		// prefill missing parts
		findAndAddMissingPartLinks(partLinks, parts, sTermsMissing);

		Map<LOINCPart, Integer> partsFrequencies = new HashMap<>();

		for (LOINCTerm term : sTermsMissing.values()) {
			LOINCTermID termID = term.getTermID();
			for (LOINCPartID partID : partLinks.get(termID)) {
				LOINCPart part = parts.get(partID);
				if (part == null) {
					log.error("EMPTY PART " + partID + termID);
					continue;
				}
				if (!(part.getPART_TYPE().equals("COMPONENT")
						|| part.getPART_TYPE().equals("METHOD")
						|| part.getPART_TYPE().equals("SYSTEM") || part
						.getPART_TYPE().equals("SUPER SYSTEM"))) {
					continue;
				}
				Integer count = partsFrequencies.get(part);
				if (count == null) {
					count = 0;
				}
				partsFrequencies.put(part, ++count);
			}
		}

		// output top 10 most popular parts
		Map<LOINCPart, Integer> sortedMap = new TreeMap<LOINCPart, Integer>(
				new ValueComparator<LOINCPart>(partsFrequencies));
		sortedMap.putAll(partsFrequencies);
		Set<LOINCPart> topParts = new HashSet<>();
		Integer totalPartsCoverage = 0;
		for (Entry<LOINCPart, Integer> e : sortedMap.entrySet()) {
			if (topParts.size() == 10) {
				break;
			}
			topParts.add(e.getKey());
			totalPartsCoverage += e.getValue();
			log.info(e.getValue() + "\t" + e.getKey());
		}
		log.info("COVERAGE IN TOP 10 " + totalPartsCoverage);

		// create all combinations of topParts
		topParts.add(null);
		Set<Set<LOINCPart>> combinations = new HashSet<>();
		combinate(topParts, combinations, new HashSet<LOINCPart>(), null, 0, 3);

		log.info("CREATED combinations " + combinations.size());

		// count popularity of combinations
		Map<Set, Integer> combFreqs = new HashMap<>();
		for (Set<LOINCPart> set : combinations) {
			for (LOINCTerm term : sTermsMissing.values()) {
				Set<LOINCPart> setMatched = new HashSet<>(set);
				for (LOINCPartID partID : partLinks.get(term.getTermID())) {
					LOINCPart part = parts.get(partID);
					setMatched.remove(part);
				}
				// we've matched them all
				if (setMatched.isEmpty()) {
					Integer count = combFreqs.get(set);
					if (count == null) {
						count = 0;
					}
					combFreqs.put(set, ++count);
				}
			}
		}

		// output top 10 most popular combinations
		Map<Set, Integer> sortedCombs = new TreeMap<Set, Integer>(
				new ValueComparator<Set>(combFreqs));
		sortedCombs.putAll(combFreqs);
		Set topCombs = new HashSet<>();
		Integer totalCombinationCoverage = 0;
		for (Entry<Set, Integer> e : sortedCombs.entrySet()) {
			if (topCombs.size() == 10) {
				break;
			}
			topCombs.add(e.getKey());
			totalCombinationCoverage += e.getValue();
			log.info(e.getValue() + "\t" + e.getKey());
		}
		log.info("COVERAGE IN TOP 10 " + totalCombinationCoverage);
	}

	private static void combinate(Set<LOINCPart> topParts,
			Set<Set<LOINCPart>> combinations, Set<LOINCPart> result,
			LOINCPart newPart, int r, int maxR) {

		// new starting set
		result = new HashSet<>(result);
		if (newPart != null) {
			result.add(newPart);
		}

		// end recurrence if depth reached
		if (r == maxR) {
			if (!result.isEmpty()) {
				combinations.add(result);
			}
			return;
		}

		for (LOINCPart part : topParts) {
			combinate(topParts, combinations, result, part, r + 1, maxR);
		}

	}

	public static void findCircularEquivalence() throws LowlincException {
		log.info("finding circular events");
		Ontologies.loadOntology(LowlincConfig.getString("INFERRED_SAVE_PATH"));
		log.info("inferred loaded");

		OWLOntology ontology = Ontologies.getOntology();

		for (OWLClass cls : ontology.getClassesInSignature()) {
			if (cls.toStringID().contains("loinc.owl#LP")) {
				// get equivalent classes
				Set<OWLClassExpression> sEquiv = cls
						.getEquivalentClasses(ontology);
				Set<OWLClass> classesEquiv = new HashSet<>();
				for (OWLClassExpression exp : sEquiv) {
					if (exp.getClassExpressionType() == ClassExpressionType.OWL_CLASS) {
						classesEquiv.add(exp.asOWLClass());
					}
				}

				// get ALL superclasses
				Set<OWLClass> classesSup = getAllParents(cls, ontology);

				// intersection of the two
				classesEquiv.retainAll(classesSup);
				if (classesEquiv.size() > 0) {
					String circular = "";
					for (OWLClass clsE : classesEquiv) {
						circular += "\t" + clsE;
					}
					log.info("CIRCULARITY IN " + cls + circular);
				}
			}
		}
	}

	public static Set<OWLClass> getAllParents(OWLClass cls, OWLOntology ontology) {
		return processStack(cls, ontology);
	}

	public static Set<OWLClass> getParents(OWLClass cls, OWLOntology ontology) {
		Set<OWLClassExpression> sSup = cls.getSuperClasses(ontology);
		Set<OWLClass> classesSup = new HashSet<>();
		for (OWLClassExpression exp : sSup) {
			if (exp.getClassExpressionType() == ClassExpressionType.OWL_CLASS) {
				classesSup.add(exp.asOWLClass());
			}
		}
		return classesSup;
	}

	private static Set<OWLClass> processStack(OWLClass seed,
			OWLOntology ontology) {
		Set<OWLClass> result = new HashSet<>();
		Stack<OWLClass> stack = new Stack<>();
		// seed the stack
		stack.add(seed);

		while (stack.size() != 0) {
			if (result.contains(stack.peek())) {
				stack.pop();
				continue;
			}
			result.add(stack.peek());
			// add to process stack
			stack.addAll(getParents(stack.pop(), ontology));
		}
		result.remove(seed);
		return result;
	}

	public static void findAndAddMissingPartLinks(
			Map<LOINCTermID, List<LOINCPartID>> partLinks,
			Map<LOINCPartID, LOINCPart> parts, Map<LOINCTermID, LOINCTerm> terms)
			throws LowlincException {

		Map<String, LOINCPart> partNames = new HashMap<>();

		for (LOINCPart part : parts.values()) {
			String partKey = part.getPART_TYPE() + part.getLabel();
			if (partNames.containsKey(partKey)) {
				log.error(partKey + " shared in " + part.getPartID() + " and "
						+ partNames.get(partKey).getPartID());
			}
			partNames.put(partKey, part);
		}

		Integer cUndefined = 0;
		Integer cMatched = 0;
		Integer cTotal = 0;
		Integer dummyID = 0;

		Map<String, TermField> fields = new HashMap<>();

		fields.put("COMPONENT", new TermField("getComponentProper"));
		fields.put("METHOD", new TermField("getMETHOD_TYP"));
		fields.put("PROPERTY", new TermField("getPROPERTY"));
		fields.put("SCALE", new TermField("getSCALE_TYP"));
		fields.put("SYSTEM", new TermField("getSystemProper"));
		fields.put("TIME", new TermField("getTIME_ASPCT"));
		fields.put("CHALLENGE", new TermField("getChallenge"));
		fields.put("ADJUSTMENT", new TermField("getAdjustment"));
		fields.put("SUPER SYSTEM", new TermField("getSuperSystem"));
		fields.put("INDEX", new TermField("getIndex"));

		// on all terms
		for (LOINCTerm term : terms.values()) {
			if (!term.isActive()) {
				continue;
			}
			LOINCTermID termID = term.getTermID();

			cTotal++;

			// reset counters
			for (TermField field : fields.values()) {
				field.resetFound();
				field.setValue(term);
			}

			// count existing links
			// create a local copy as I'm removing links inside the loop
			// later
			for (LOINCPartID partLink : new ArrayList<LOINCPartID>(
					partLinks.get(termID))) {
				LOINCPart part = parts.get(partLink);
				// check existing relations
				// skip missing parts
				if (part == null) {
					continue;
				}
				// remove incorrect prime links
				String partType = part.getPART_TYPE();
				if (partType.equals("COMPONENT")) {
					String partValue = part.getLabel();
					String compValue = term.getCOMPONENT();
					// S' wave dur L-V6 === S wave dur L-V6
					// if term's component or component part
					// contain a prime (') and their absolute
					// length differ by one, remove the part link
					if (partValue.length() > 1
							&& compValue.length() > 1
							&& (partValue.substring(1, 2).equals("'") || compValue
									.substring(1, 2).equals("'"))
							&& Math.abs(partValue.length() - compValue.length()) == 1) {
						log.warn("PRIME PARTLINK " + term + " " + part);
						// want to see those in duplicate list after all
						// partLinks.get(termID).remove(partLink);
						continue;
					}

				}

				if (fields.containsKey(partType)) {
					fields.get(partType).incrementFound();
				}

			}

			// count empty fields as valid links
			for (TermField field : fields.values()) {
				String fieldValue = field.getValue();
				if (fieldValue.equals("") || fieldValue.equals("-")
						|| fieldValue.equals("*") || fieldValue.equals("?")) {
					field.incrementFound();
				}
			}

			// MATCHING
			for (String partType : fields.keySet()) {
				TermField field = fields.get(partType);
				if (!field.isFound()) {
					LOINCPart matchedPart = matchPart(partNames,
							field.getValue(), partType);
					if (matchedPart != null) {
						field.incrementFound();
						field.incrementMatched();
						cMatched++;
					} else {
						field.incrementMissing();
						// create dummy
						String dummyKey = partType + field.getValue();
						if (partNames.containsKey(dummyKey)) {
							matchedPart = partNames.get(dummyKey);
						} else {
							matchedPart = new LOINCPart();
							matchedPart.setPART_TYPE(partType);
							matchedPart.setL_PART_ID("LPdummy_" + ++dummyID);
							matchedPart.setNAME_CASE_INSENSITIVE(field
									.getValue());
							// partNames.put(dummyKey, matchedPart);
							// parts.put(matchedPart.getPartID(), matchedPart);
							log.info("UNMATCHED " + matchedPart);
						}

					}
					// add link
					partLinks.get(termID).add(matchedPart.getPartID());
				}
			}

			// need to do this after the match
			// as a lot of missing component links will be relinked
			// collect what is defined by parts
			// in terms of subdots parts
			String definedComponentDots = "";
			for (LOINCPartID partLink : partLinks.get(termID)) {
				LOINCPart part = parts.get(partLink);
				// skip missing parts
				if (part == null) {
					continue;
				}

				String partType = part.getPART_TYPE();
				if (partType.equals("COMPONENT")
						|| partType.equals("FRAGMENTS FOR SYNONYMS")) {
					definedComponentDots += ":" + part.getLabel();
				}

			}
			// now check if component's subparts are defined by existing links
			if (term.getComponentProper().contains(".")) {
				for (String dotPart : term.getComponentProper().split("\\.")) {
					if (!definedComponentDots.contains(dotPart)) {
						log.debug("MISSING COMPONENT PART " + dotPart + " IN "
								+ term);
					}
				}
			}

			// are all axes defined
			Boolean missing = false;
			String linkCounts = "";
			String linkValues = "";
			for (String key : fields.keySet()) {
				TermField field = fields.get(key);
				if (!field.isFound()) {
					missing = true;

					linkValues += key + " " + field.getValue() + " ";
					linkCounts += key + " " + field.getFoundCounter() + " ";
				}
			}

			if (missing) {
				cUndefined++;

				log.info("MISSING PART LINK " + termID + " " + linkCounts);
				log.info("\t" + "EXISTING " + linkValues);
				log.info("\t" + "LABEL " + term.getLabel());

			}
		}

		String totalStats = "";
		String totalMatched = "";
		for (String key : fields.keySet()) {
			TermField field = fields.get(key);
			totalStats += key + " " + field.getMissingCounter() + " ";
			totalMatched += key + " " + field.getMatchedCounter() + " ";
		}

		log.info("Total missing stats: " + totalStats);
		log.info(cMatched + " total matched: " + totalMatched);
		log.info(cUndefined + " undefined terms found out of " + cTotal
				+ " total");

	}

	private static LOINCPart matchPart(Map<String, LOINCPart> partNames,
			String label, String type) {
		String key = type + label;
		if (partNames.containsKey(key)) {
			log.debug("MATCHED " + partNames.get(key));
			return partNames.get(key);
		}
		return null;
	}

	public static void compareOntologies() throws LowlincException {
		log.info("running compare");
		log.info("loading base");
		Ontologies.loadOntology(LowlincConfig.getString("SAVE_PATH"));
		OWLOntology ontology1 = Ontologies.getOntology();
		log.info("inferred loaded");
		Ontologies.loadOntology(LowlincConfig.getString("INFERRED_SAVE_PATH"));
		OWLOntology ontology2 = Ontologies.getOntology();

		// save original axioms
		Set<OWLAxiom> assertedAxioms = ontology1.getAxioms();
		Set<OWLAxiom> inferredAxioms = ontology2.getAxioms();

		log.info("Number of asserted axioms: " + assertedAxioms.size());
		log.info("Number of inferred axioms (CONDOR): " + inferredAxioms.size());
		inferredAxioms.removeAll(assertedAxioms);
		log.info("Number of inferred axioms after subtraction: "
				+ inferredAxioms.size());
		Integer subclassInferred = 0;
		Integer equivalentInferred = 0;

		Integer equivalentLPsets = 0;
		Integer equivalentLTsets = 0;

		Set<OWLClass> equivalentLPs = new HashSet<>();
		Set<OWLClass> equivalentLTs = new HashSet<>();
		PrintWriter out;
		try {
			log.info("Saving potential duplicates to "
					+ LowlincConfig.getString("DUPLICATES.path"));
			out = new PrintWriter(new FileWriter(
					LowlincConfig.getString("DUPLICATES.path")));
		} catch (IOException e) {
			throw new LowlincException(e);
		}

		for (OWLAxiom axiom : inferredAxioms) {
			if (axiom.getAxiomType().equals(AxiomType.SUBCLASS_OF)) {
				Set<OWLClass> LP = new HashSet<>();
				Set<OWLClass> LT = new HashSet<>();
				// check classes in signature
				for (OWLClass cls : ((OWLSubClassOfAxiom) axiom)
						.getClassesInSignature()) {
					if (cls.toStringID().contains("loinc.owl#LP")) {
						LP.add(cls);
					} else if (cls.toStringID().contains("loinc.owl")) {
						LT.add(cls);
					}
				}
				if (LT.size() > 0 || LP.size() > 0) {
					subclassInferred++;
				}
			}
			if (axiom.getAxiomType().equals(AxiomType.EQUIVALENT_CLASSES)) {

				Set<OWLClass> LP = new HashSet<>();
				Set<OWLClass> LT = new HashSet<>();
				// check classes in signature
				String equivs = "";
				for (OWLClass cls : ((OWLEquivalentClassesAxiom) axiom)
						.getClassesInSignature()) {
					if (cls.toStringID().contains("loinc.owl#LP")) {
						LP.add(cls);
					} else if (cls.toStringID().contains("loinc.owl")) {
						LT.add(cls);
						equivs += "\t" + cls.getIRI().getFragment();
					}
				}
				// set flags
				if (LP.size() > 1) {
					equivalentLPsets++;
					equivalentLPs.addAll(LP);
				}
				if (LT.size() > 1) {
					equivalentLTsets++;
					equivalentLTs.addAll(LT);
				}
				if (LT.size() > 0 || LP.size() > 0) {
					equivalentInferred++;
				}
				if (!equivs.equals("")) {
					out.println(equivs);
				}
			}
		}

		log.info("\t" + subclassInferred
				+ " inferred SUBCLASS_OF axioms involving LOINC");
		log.info("\t" + equivalentInferred
				+ " inferred EQUIVALENT_CLASSES axioms involving LOINC");

		log.info(equivalentLPsets + " inferred equivalent LOINC part sets");
		log.info("\tcomprising " + equivalentLPs.size() + " LOINC parts");

		log.info(equivalentLTsets + " inferred equivalent LOINC term sets");
		log.info("\tcomprising " + equivalentLTs.size() + " LOINC terms");

		out.println("<SET FOLLOWS>");
		for (OWLClass cls : equivalentLTs) {
			out.println(cls.getIRI().getFragment());
		}

		out.close();
		// log.info("Sorting inferred axioms for display");
		// OWLAxiom[] listAxioms = inferredAxioms.toArray(new OWLAxiom[0]);
		// Arrays.sort(listAxioms, new OWLAxiomComparator());
		//
		// for (OWLAxiom axiom : listAxioms) {
		// log.info("Inferred axiom: " + axiom.toString());
		// }

	}

	public static String getRank(OWLDataFactory factory, OWLOntology ontology,
			OWLClass cls) {
		OWLAnnotationProperty annot = factory.getOWLAnnotationProperty(IRI
				.create("http://www.loinc.org/loinc.owl#COMMON_TEST_RANK"));
		Set<OWLAnnotation> annotSet = cls.getAnnotations(ontology, annot);

		if (annotSet.isEmpty()) {
			return null;
		}

		return ((OWLLiteral) annotSet.toArray(new OWLAnnotation[0])[0]
				.getValue()).getLiteral();
	}

	public static void debugEquivalences() throws LowlincException {
		log.info("loading owl");
		log.info("base loaded");
		Ontologies.loadOntology(LowlincConfig.getString("SAVE_PATH"));

		showClassInfo("http://www.loinc.org/loinc.owl#LP29159-8");
		showClassInfo("http://www.loinc.org/loinc.owl#LP29835-3");
		showClassInfo("http://www.ihtsdo.org/SCT_272673000");

	}

	private static void showClassInfo(String url) throws LowlincException {
		OWLOntology ontology = Ontologies.getOntology();
		IRI iri = IRI.create(url);

		OWLClass cls = Ontologies.getManager().getOWLDataFactory()
				.getOWLClass(iri);
		log.info("");
		log.info("");
		log.info(iri);
		log.info(Ontologies.getLabel(cls));
		log.info("");
		log.info("\tgetAxioms(cls)");
		for (OWLAxiom axiom : ontology.getAxioms(cls)) {
			log.info(axiom);
		}
		log.info("");
		log.info("\tcls.getEquivalentClasses");
		for (OWLClassExpression expression : cls.getEquivalentClasses(ontology)) {
			log.info(expression);
		}
		log.info("");
		log.info("\tcls.getSubClasses");
		for (OWLClassExpression expression : cls.getSubClasses(ontology)) {
			log.info(expression);
		}

		log.info("");
		log.info("\tcls.getSuperClasses");
		for (OWLClassExpression expression : cls.getSuperClasses(ontology)) {
			log.info(expression);
		}
	}

}
